package org.nViron.nvRender;

//~--- non-JDK imports --------------------------------------------------------

import org.nViron.nvCommon.nvPosition;
import org.nViron.nvSim.nvSimulator;

/**
 * 3d Frustum used for culling boxes and spheres
 * @author Roland Haring <br>
 * based on code by <a href="http://www.lighthouse3d.com/opengl/viewfrustum/">Lighthouse3d</a> <p>
 * <script type="text/javascript" src="http://www.ohloh.net/p/376434/widgets/project_basic_stats.js"></script>
 */
public class nvFrustum {
    public enum ALIGN { OUTSIDE, INTERSECT, INSIDE }
    final protected float ANG2RAD = 3.14159265358979323846f / 180.0f;
    final public int      BOTTOM  = 1;
    final public int      FARP    = 5;
    final public int      GROUND  = 6;
    final public int      LEFT    = 2;
    final public int      NEARP   = 4;
    final public int      RIGHT   = 3;
    final public int      TOP     = 0;
    public nvVec3         gCent;    // center of ground plane
    public nvVec3         gnl, gnr, gfl, gfr;    // [ground][nearplane|farplane][left|right] vertices of ground plane
    protected float       nearP, farP, aspect, fov, tang;
    public nvVec3         ntl, ntr, nbl, nbr, ftl, ftr, fbl, fbr;    // [nearplane|farplane][bottom|top][left|right] vertices of frustum
    protected float nw, nh, fw, fh;

    public nvPlane  pl[];

    ;
    public nvFrustum() {
        pl = new nvPlane[7];

        for (int i = 0; i < 7; i++) {
            pl[i] = new nvPlane();
        }

        // ground plane is constant
        pl[GROUND].set3Points(new nvVec3(0, 0, 0), new nvVec3(1, 0, 0), new nvVec3(0, 0, 1));
        ntl   = new nvVec3();
        ntr   = new nvVec3();
        nbl   = new nvVec3();
        nbr   = new nvVec3();
        ftl   = new nvVec3();
        ftr   = new nvVec3();
        fbl   = new nvVec3();
        fbr   = new nvVec3();
        gnl   = new nvVec3();
        gnr   = new nvVec3();
        gfl   = new nvVec3();
        gfr   = new nvVec3();
        gCent = new nvVec3();
    }

    public ALIGN boxInFrustum(nvAABox b) {

/*
         // optimization using bounding box for frustum fb_ and bounding box for object ob_
        if (fb_xmin > ob_xmax || fb_xmax < ob_xmin ||
                fb_ymin > ob_ymax || fb_ymax < ob_ymin ||
                fb_zmin > ob_zmax || fb_zmax < ob_zmin)

                return (OUTSIDE);

        else if (fb_xmin < ob_xmin && fb_xmax > ob_xmax &&
                         fb_ymin < ob_ymin && fb_ymax > ob_ymax &&
                         fb_zmin < ob_zmin && fb_zmax > ob_zmax)
                return (INSIDE);
        else
                return(INTERSECT);
*/
        ALIGN result = ALIGN.INSIDE;

        for (int i = 0; i < 6; i++) {
            if (pl[i].distance(b.getVertexP(pl[i].normal)) < 0) {
                return ALIGN.OUTSIDE;
            } else if (pl[i].distance(b.getVertexN(pl[i].normal)) < 0) {
                result = ALIGN.INTERSECT;
            }
        }

        return (result);
    }

    public boolean doesIntersectGround() {
        return (gnl != null) && (gnr != null) && (gfl != null) && (gfr != null);
    }

    public boolean doIntersect(nvPlane pl1, nvPlane pl2, nvPlane pl3) {

        // planes are parallell if N1 . ( N2 * N3 ) = 0
        return pl1.normal.dot(pl2.normal.cross(pl3.normal)) != 0;
    }

    public float getAspect() {
        return aspect;
    }

    public float getFarP() {
        return farP;
    }

    public float getFOV() {
        return fov;
    }

    public float getNearP() {
        return nearP;
    }

    public ALIGN pointInFrustum(nvVec3 p) {
        ALIGN result = ALIGN.INSIDE;

        for (int i = 0; i < 6; i++) {
            if (pl[i].distance(p) < 0) {
                return ALIGN.OUTSIDE;
            }
        }

        return (result);
    }

    public ALIGN posInFrustum(nvPosition pos) {
        nvVec3 p = nvSimulator.getInstance().getProjection().geoToViz(pos);

        return pointInFrustum(p);
    }

    public void setAspect(float aspect) {
        this.aspect = aspect;
    }

    public void setCamera(nvVec3 p, nvVec3 l, nvVec3 u) {
        nvVec3 nc, fc, X, Y, Z;

        Z = p.sub(l);
        Z.normalize();
        X = u.cross(Z);
        X.normalize();
        Y   = Z.cross(X);
        nc  = p.sub(Z.mul(nearP));
        fc  = p.sub(Z.mul(farP));
        ntl = nc.add(Y.mul(nh)).sub(X.mul(nw));
        ntr = nc.add(Y.mul(nh)).add(X.mul(nw));
        nbl = nc.sub(Y.mul(nh)).sub(X.mul(nw));
        nbr = nc.sub(Y.mul(nh)).add(X.mul(nw));
        ftl = fc.add(Y.mul(fh)).sub(X.mul(fw));
        ftr = fc.add(Y.mul(fh)).add(X.mul(fw));
        fbl = fc.sub(Y.mul(fh)).sub(X.mul(fw));
        fbr = fc.sub(Y.mul(fh)).add(X.mul(fw));
        pl[TOP].set3Points(ntr, ntl, ftl);
        pl[BOTTOM].set3Points(nbl, nbr, fbr);
        pl[LEFT].set3Points(ntl, nbl, fbl);
        pl[RIGHT].set3Points(nbr, ntr, fbr);
        pl[NEARP].set3Points(ntl, ntr, nbr);
        pl[FARP].set3Points(ftr, ftl, fbl);
        gnl = threePlaneIntersection(pl[GROUND], pl[NEARP], pl[LEFT]);
        gnr = threePlaneIntersection(pl[GROUND], pl[NEARP], pl[RIGHT]);
        gfl = threePlaneIntersection(pl[GROUND], pl[FARP], pl[LEFT]);
        gfr = threePlaneIntersection(pl[GROUND], pl[FARP], pl[RIGHT]);

        if (doesIntersectGround()) {
            gCent.x = (gnl.x + gnr.x + gfl.x + gfr.x) / 4.0f;
            gCent.y = (gnl.y + gnr.y + gfl.y + gfr.y) / 4.0f;
            gCent.z = (gnl.z + gnr.z + gfl.z + gfr.z) / 4.0f;
        }
    }

    public void setFarP(float farP) {
        this.farP = farP;
    }

    public void setFOV(float fov) {
        this.fov = fov;
    }

    public void setNearP(float nearP) {
        this.nearP = nearP;
    }

    public void setProjection(float angle, float ratio, float nearD, float farD) {
        this.aspect = ratio;
        this.fov    = angle;
        this.nearP  = nearD;
        this.farP   = farD;
        tang        = (float) Math.tan(angle * ANG2RAD * 0.5);
        nh          = nearD * tang;
        nw          = nh * ratio;
        fh          = farD * tang;
        fw          = fh * ratio;
    }

    public ALIGN sphereInFrustum(nvVec3 p, float raio) {
        ALIGN result = ALIGN.INSIDE;
        float distance;

        for (int i = 0; i < 6; i++) {
            distance = pl[i].distance(p);

            if (distance < -raio) {
                return ALIGN.OUTSIDE;
            } else if (distance < raio) {
                result = ALIGN.INTERSECT;
            }
        }

        return (result);
    }

    public nvVec3 threePlaneIntersection(nvPlane pl1, nvPlane pl2, nvPlane pl3) {
        nvVec3 ret     = null;
        float  divisor = pl1.normal.dot(pl2.normal.cross(pl3.normal));

        if (divisor == 0) {
            return ret;
        }

//      http://local.wasp.uwa.edu.au/~pbourke/geometry/3planes/
//      The intersection of three planes is either a point, a line, or there is no intersection (any two of the planes are parallel).
//      The three planes can be written as
//      N1 . p = d1
//      N2 . p = d2
//      N3 . p = d3
//      In the above and what follows, "." signifies the dot product and "*" is the cross product. The intersection point P is given by:
//           d1 ( N2 * N3 ) + d2 ( N3 * N1 ) + d3 ( N1 * N2 )
//      P =  -------------------------------------------------------------------------
//           N1 . ( N2 * N3 )
//      The denominator is zero if N2 * N3 = 0, in other words the planes are parallel. Or if N1 is a linear combination of N2 and N3.
        nvVec3 dividend = pl2.normal.cross(pl3.normal).mul(-pl1.d).add(
                              pl3.normal.cross(pl1.normal).mul(-pl2.d).add(pl1.normal.cross(pl2.normal).mul(-pl3.d)));

        ret = dividend.div(divisor);

        return ret;
    }
}



